#pragma once

#include "string_format.hpp"
#include "tuple_util.hpp"

#include <algorithm>
#include <cstdint>
#include <cstring>
#include <list>
#include <string>
#include <string_view>
#include <vector>

namespace kratos {
namespace util {

namespace scan_internal {

#define ScanBasic(type) template <> inline void operator()(type &value);

#define ScanBasicImpl(type)                                                    \
  template <> inline void Pusher::operator()(type &value) {                    \
    value = slexical_cast<type>(std::string(it_->begin(), it_->end()));        \
    advance();                                                                 \
  }

class Pusher {
  std::list<std::string_view>::iterator it_;
  std::list<std::string_view> *view_{nullptr};

private:
  inline auto advance() {
    if (it_ != view_->end()) {
      ++it_;
    }
  }

public:
  Pusher(std::list<std::string_view> *view,
         std::list<std::string_view>::iterator view_it) {
    view_ = view;
    it_ = view_it;
  }

  template <typename ItemType> inline void operator()(ItemType &item) {
    std::string_view value(it_->data(), it_->size());
    value >> item;
    advance();
  }

  template <> inline void operator()(std::string &value) {
    value.assign(it_->data(), it_->size());
    advance();
  }

  ScanBasic(std::int8_t);
  ScanBasic(std::int16_t);
  ScanBasic(std::int32_t);
  ScanBasic(std::int64_t);
  ScanBasic(std::uint8_t);
  ScanBasic(std::uint16_t);
  ScanBasic(std::uint32_t);
  ScanBasic(std::uint64_t);
  ScanBasic(float);
  ScanBasic(double);
#ifdef _MSC_VER
  ScanBasic(long);
  ScanBasic(unsigned long);
#endif // _MSC_VER
};

ScanBasicImpl(std::int8_t);
ScanBasicImpl(std::int16_t);
ScanBasicImpl(std::int32_t);
ScanBasicImpl(std::int64_t);
ScanBasicImpl(std::uint8_t);
ScanBasicImpl(std::uint16_t);
ScanBasicImpl(std::uint32_t);
ScanBasicImpl(std::uint64_t);
ScanBasicImpl(float);
ScanBasicImpl(double);
#ifdef _MSC_VER
ScanBasicImpl(long);
ScanBasicImpl(unsigned long);
#endif // _MSC_VER

} // namespace scan_internal

template <typename... ArgsT>
inline static auto scan(std::string_view input, std::string_view fmt,
                        ArgsT &...args) -> void {
  using ViewList = std::list<std::string_view>;
  //
  // Find all '{}', store charater in a vector
  //
  std::size_t fmt_pos = 0, input_last_pos = 0, input_pos = 0;
  bool in_fmt = false;
  ViewList view_list;
  for (auto it = fmt.begin(); it != fmt.end();) {
    auto c = *it;
    if (!in_fmt) {
      if (c == '{') {
        if (fmt_pos + 1 < fmt.size() && fmt[fmt_pos + 1] == '}') {
          in_fmt = true;
          input_last_pos = input_pos;
          if (fmt_pos + 2 < fmt.size()) {
            fmt_pos += 2;
            std::advance(it, 2);
          }
        } else {
          break;
        }
      } else {
        ++it;
        input_pos += 1;
        fmt_pos += 1;
      }
    } else {
      if (input_pos >= input.size()) {
        if (input_pos > input_last_pos) {
          view_list.push_back(std::string_view(input.data() + input_last_pos,
                                               input_pos - input_last_pos));
        }
        break;
      }
      if (input[input_pos] == c) {
        view_list.push_back(std::string_view(input.data() + input_last_pos,
                                             input_pos - input_last_pos));
        in_fmt = false;
      } else {
        input_pos += 1;
      }
    }
  }
  auto tuple_args = std::tie(args...);
  auto tuple_size = std::tuple_size<decltype(tuple_args)>::value;
  //
  // Check the count of argument
  //
#ifdef UNSAFE_CODE
  if (view_list.size() != tuple_size) {
#if defined(DEBUG) || defined(_DEBUG)
    throw std::runtime_error("format method argument mismatch");
#endif // defined(DEBUG) || defined(_DEBUG)
  }
#endif // UNSAFE_CODE
  decltype(tuple_size) iter_n =
      view_list.size() > tuple_size ? tuple_size : view_list.size();
  if (iter_n > 0) {
    tuple_foreach_ref(tuple_args,
                      scan_internal::Pusher(&view_list, view_list.begin()));
  }
}

template <typename... ArgsT>
inline static auto scan(const std::string &input, const std::string &fmt,
                        ArgsT &...args) -> void {
  return scan(std::string_view(input.data(), input.size()),
              std::string_view(fmt.data(), fmt.size()), args...);
}

template <typename... ArgsT>
inline static auto scan(const char *const input, const std::string &fmt,
                        ArgsT &...args) -> void {
  return scan(std::string_view(input, std::strlen(input)),
              std::string_view(fmt.data(), fmt.size()), args...);
}

template <typename... ArgsT>
inline static auto scan(const char *const input, const char *const fmt,
                        ArgsT &...args) -> void {
  return scan(std::string_view(input, std::strlen(input)),
              std::string_view(fmt, std::strlen(fmt)), args...);
}

template <typename... ArgsT>
inline static auto scan(const std::string &input, const char *const fmt,
                        ArgsT &...args) -> void {
  return scan(std::string_view(input.data(), input.size()),
              std::string_view(fmt, std::strlen(fmt)), args...);
}

} // namespace util
} // namespace kratos

#define sscan(...) kratos::util::scan(__VA_ARGS__);
